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
Share telemetry central logger instance between ProjectCollection and…
… Build calls

Co-authored-by: baronfel <[email protected]>
  • Loading branch information
Copilot and baronfel committed Dec 5, 2025
commit c2017345632cc97bd7867fadece7277109435b85
25 changes: 15 additions & 10 deletions src/Cli/dotnet/Commands/Run/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -470,31 +470,36 @@ internal ICommand GetTargetCommand(Func<ProjectCollection, ProjectInstance>? pro

Reporter.Verbose.WriteLine("Getting target command: evaluating project.");
FacadeLogger? logger = LoggerUtility.DetermineBinlogger([.. MSBuildArgs.OtherMSBuildArgs], "dotnet-run");
var project = EvaluateProject(ProjectFileFullPath, projectFactory, MSBuildArgs, logger);
var (project, telemetryCentralLogger) = EvaluateProject(ProjectFileFullPath, projectFactory, MSBuildArgs, logger);
ValidatePreconditions(project);
InvokeRunArgumentsTarget(project, NoBuild, logger, MSBuildArgs);
InvokeRunArgumentsTarget(project, NoBuild, logger, MSBuildArgs, telemetryCentralLogger);
logger?.ReallyShutdown();
var runProperties = RunProperties.FromProject(project).WithApplicationArguments(ApplicationArgs);
var command = CreateCommandFromRunProperties(runProperties);
return command;

static ProjectInstance EvaluateProject(string? projectFilePath, Func<ProjectCollection, ProjectInstance>? projectFactory, MSBuildArgs msbuildArgs, ILogger? binaryLogger)
static (ProjectInstance project, ILogger? telemetryCentralLogger) EvaluateProject(string? projectFilePath, Func<ProjectCollection, ProjectInstance>? projectFactory, MSBuildArgs msbuildArgs, ILogger? binaryLogger)
{
Debug.Assert(projectFilePath is not null || projectFactory is not null);

var globalProperties = CommonRunHelpers.GetGlobalPropertiesFromArgs(msbuildArgs);

// Include telemetry logger for evaluation
var loggers = ProjectInstanceExtensions.CreateLoggersWithTelemetry(binaryLogger is null ? null : [binaryLogger]);
// Include telemetry logger for evaluation and capture it for reuse in builds
var (loggers, telemetryCentralLogger) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(binaryLogger is null ? null : [binaryLogger]);
var collection = new ProjectCollection(globalProperties: globalProperties, loggers: loggers, toolsetDefinitionLocations: ToolsetDefinitionLocations.Default);

ProjectInstance projectInstance;
if (projectFilePath is not null)
{
return collection.LoadProject(projectFilePath).CreateProjectInstance();
projectInstance = collection.LoadProject(projectFilePath).CreateProjectInstance();
}
else
{
Debug.Assert(projectFactory is not null);
projectInstance = projectFactory(collection);
}

Debug.Assert(projectFactory is not null);
return projectFactory(collection);
return (projectInstance, telemetryCentralLogger);
}

static void ValidatePreconditions(ProjectInstance project)
Expand Down Expand Up @@ -550,7 +555,7 @@ static ICommand CreateCommandForCscBuiltProgram(string entryPointFileFullPath, s
return command;
}

static void InvokeRunArgumentsTarget(ProjectInstance project, bool noBuild, FacadeLogger? binaryLogger, MSBuildArgs buildArgs)
static void InvokeRunArgumentsTarget(ProjectInstance project, bool noBuild, FacadeLogger? binaryLogger, MSBuildArgs buildArgs, ILogger? telemetryCentralLogger)
{
List<ILogger> loggersForBuild = [
CommonRunHelpers.GetConsoleLogger(
Expand All @@ -562,7 +567,7 @@ static void InvokeRunArgumentsTarget(ProjectInstance project, bool noBuild, Faca
loggersForBuild.Add(binaryLogger);
}

if (!project.BuildWithTelemetry([Constants.ComputeRunArguments], loggersForBuild, null, out _))
if (!project.BuildWithTelemetry([Constants.ComputeRunArguments], loggersForBuild, null, out _, telemetryCentralLogger))
{
throw new GracefulException(CliCommandStrings.RunCommandEvaluationExceptionBuildFailed, Constants.ComputeRunArguments);
}
Expand Down
18 changes: 10 additions & 8 deletions src/Cli/dotnet/Commands/Test/MTP/MSBuildUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Build.Evaluation;
using Microsoft.Build.Evaluation.Context;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.DotNet.Cli.Commands.Restore;
using Microsoft.DotNet.Cli.Commands.Run;
using Microsoft.DotNet.Cli.CommandLine;
Expand Down Expand Up @@ -67,11 +68,11 @@ public static (IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModul

FacadeLogger? logger = LoggerUtility.DetermineBinlogger([.. buildOptions.MSBuildArgs], dotnetTestVerb);

// Include telemetry logger for evaluation
var loggers = ProjectInstanceExtensions.CreateLoggersWithTelemetry(logger is null ? null : [logger]);
// Include telemetry logger for evaluation and capture it for reuse in builds
var (loggers, telemetryCentralLogger) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(logger is null ? null : [logger]);
using var collection = new ProjectCollection(globalProperties: globalProperties, loggers: loggers, toolsetDefinitionLocations: ToolsetDefinitionLocations.Default);
var evaluationContext = EvaluationContext.Create(EvaluationContext.SharingPolicy.Shared);
ConcurrentBag<ParallelizableTestModuleGroupWithSequentialInnerModules> projects = GetProjectsProperties(collection, evaluationContext, projectPaths, buildOptions);
ConcurrentBag<ParallelizableTestModuleGroupWithSequentialInnerModules> projects = GetProjectsProperties(collection, evaluationContext, projectPaths, buildOptions, telemetryCentralLogger);
logger?.ReallyShutdown();
collection.UnloadAllProjects();

Expand All @@ -91,11 +92,11 @@ public static (IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModul

var msbuildArgs = MSBuildArgs.AnalyzeMSBuildArguments(buildOptions.MSBuildArgs, CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CommonOptions.MSBuildTargetOption(), CommonOptions.VerbosityOption(), CommonOptions.NoLogoOption());

// Include telemetry logger for evaluation
var loggers = ProjectInstanceExtensions.CreateLoggersWithTelemetry(logger is null ? null : [logger]);
// Include telemetry logger for evaluation and capture it for reuse in builds
var (loggers, telemetryCentralLogger) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(logger is null ? null : [logger]);
using var collection = new ProjectCollection(globalProperties: CommonRunHelpers.GetGlobalPropertiesFromArgs(msbuildArgs), loggers, toolsetDefinitionLocations: ToolsetDefinitionLocations.Default);
var evaluationContext = EvaluationContext.Create(EvaluationContext.SharingPolicy.Shared);
IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> projects = SolutionAndProjectUtility.GetProjectProperties(projectFilePath, collection, evaluationContext, buildOptions, configuration: null, platform: null);
IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> projects = SolutionAndProjectUtility.GetProjectProperties(projectFilePath, collection, evaluationContext, buildOptions, telemetryCentralLogger, configuration: null, platform: null);
logger?.ReallyShutdown();
collection.UnloadAllProjects();
return (projects, isBuiltOrRestored);
Expand Down Expand Up @@ -168,7 +169,8 @@ private static ConcurrentBag<ParallelizableTestModuleGroupWithSequentialInnerMod
ProjectCollection projectCollection,
EvaluationContext evaluationContext,
IEnumerable<(string ProjectFilePath, string? Configuration, string? Platform)> projects,
BuildOptions buildOptions)
BuildOptions buildOptions,
ILogger? telemetryCentralLogger)
{
var allProjects = new ConcurrentBag<ParallelizableTestModuleGroupWithSequentialInnerModules>();

Expand All @@ -179,7 +181,7 @@ private static ConcurrentBag<ParallelizableTestModuleGroupWithSequentialInnerMod
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
(project) =>
{
IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> projectsMetadata = SolutionAndProjectUtility.GetProjectProperties(project.ProjectFilePath, projectCollection, evaluationContext, buildOptions, project.Configuration, project.Platform);
IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> projectsMetadata = SolutionAndProjectUtility.GetProjectProperties(project.ProjectFilePath, projectCollection, evaluationContext, buildOptions, telemetryCentralLogger, project.Configuration, project.Platform);
foreach (var projectMetadata in projectsMetadata)
{
allProjects.Add(projectMetadata);
Expand Down
27 changes: 18 additions & 9 deletions src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Build.Evaluation;
using Microsoft.Build.Evaluation.Context;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.DotNet.Cli.Commands.Run;
using Microsoft.DotNet.Cli.Commands.Run.LaunchSettings;
using Microsoft.DotNet.Cli.Extensions;
Expand Down Expand Up @@ -220,13 +221,21 @@ private static ProjectInstance EvaluateProject(
});
}

public static string GetRootDirectory(string solutionOrProjectFilePath)
{
string? fileDirectory = Path.GetDirectoryName(solutionOrProjectFilePath);
Debug.Assert(fileDirectory is not null);
return string.IsNullOrEmpty(fileDirectory) ? Directory.GetCurrentDirectory() : fileDirectory;
}

public static IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> GetProjectProperties(
string projectFilePath,
ProjectCollection projectCollection,
EvaluationContext evaluationContext,
BuildOptions buildOptions,
string? configuration,
string? platform)
ILogger? telemetryCentralLogger = null,
string? configuration = null,
string? platform = null)
{
var projects = new List<ParallelizableTestModuleGroupWithSequentialInnerModules>();
ProjectInstance projectInstance = EvaluateProject(projectCollection, evaluationContext, projectFilePath, tfm: null, configuration, platform);
Expand All @@ -238,7 +247,7 @@ public static IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModule

if (!string.IsNullOrEmpty(targetFramework) || string.IsNullOrEmpty(targetFrameworks))
{
if (GetModuleFromProject(projectInstance, buildOptions) is { } module)
if (GetModuleFromProject(projectInstance, buildOptions, telemetryCentralLogger) is { } module)
{
projects.Add(new ParallelizableTestModuleGroupWithSequentialInnerModules(module));
}
Expand Down Expand Up @@ -266,7 +275,7 @@ public static IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModule
projectInstance = EvaluateProject(projectCollection, evaluationContext, projectFilePath, framework, configuration, platform);
Logger.LogTrace($"Loaded inner project '{Path.GetFileName(projectFilePath)}' has '{ProjectProperties.IsTestingPlatformApplication}' = '{projectInstance.GetPropertyValue(ProjectProperties.IsTestingPlatformApplication)}' (TFM: '{framework}').");

if (GetModuleFromProject(projectInstance, buildOptions) is { } module)
if (GetModuleFromProject(projectInstance, buildOptions, telemetryCentralLogger) is { } module)
{
projects.Add(new ParallelizableTestModuleGroupWithSequentialInnerModules(module));
}
Expand All @@ -280,7 +289,7 @@ public static IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModule
projectInstance = EvaluateProject(projectCollection, evaluationContext, projectFilePath, framework, configuration, platform);
Logger.LogTrace($"Loaded inner project '{Path.GetFileName(projectFilePath)}' has '{ProjectProperties.IsTestingPlatformApplication}' = '{projectInstance.GetPropertyValue(ProjectProperties.IsTestingPlatformApplication)}' (TFM: '{framework}').");

if (GetModuleFromProject(projectInstance, buildOptions) is { } module)
if (GetModuleFromProject(projectInstance, buildOptions, telemetryCentralLogger) is { } module)
{
innerModules ??= new List<TestModule>();
innerModules.Add(module);
Expand All @@ -297,7 +306,7 @@ public static IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModule
return projects;
}

private static TestModule? GetModuleFromProject(ProjectInstance project, BuildOptions buildOptions)
private static TestModule? GetModuleFromProject(ProjectInstance project, BuildOptions buildOptions, ILogger? telemetryCentralLogger = null)
{
_ = bool.TryParse(project.GetPropertyValue(ProjectProperties.IsTestProject), out bool isTestProject);
_ = bool.TryParse(project.GetPropertyValue(ProjectProperties.IsTestingPlatformApplication), out bool isTestingPlatformApplication);
Expand All @@ -315,7 +324,7 @@ public static IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModule
RunProperties runProperties;
if (isTestingPlatformApplication)
{
runProperties = GetRunProperties(project);
runProperties = GetRunProperties(project, telemetryCentralLogger);

// dotnet run throws the same if RunCommand is null or empty.
// In dotnet test, we are additionally checking that RunCommand is not dll.
Expand Down Expand Up @@ -355,15 +364,15 @@ public static IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModule

return new TestModule(runProperties, PathUtility.FixFilePath(projectFullPath), targetFramework, isTestingPlatformApplication, launchSettings, project.GetPropertyValue(ProjectProperties.TargetPath), rootVariableName);

static RunProperties GetRunProperties(ProjectInstance project)
static RunProperties GetRunProperties(ProjectInstance project, ILogger? telemetryCentralLogger)
{
// Build API cannot be called in parallel, even if the projects are different.
// Otherwise, BuildManager in MSBuild will fail:
// System.InvalidOperationException: The operation cannot be completed because a build is already in progress.
// NOTE: BuildManager is singleton.
lock (s_buildLock)
{
if (!project.BuildWithTelemetry(s_computeRunArgumentsTarget))
if (!project.BuildWithTelemetry(s_computeRunArgumentsTarget, telemetryCentralLogger: telemetryCentralLogger))
{
throw new GracefulException(CliCommandStrings.RunCommandEvaluationExceptionBuildFailed, s_computeRunArgumentsTarget[0]);
}
Expand Down
Loading